﻿using DotNetCommon.Extensions;
using DotNetCommon.Serialize;
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace DotNetCommon;

/// <summary>
/// json帮助类
/// </summary>
public static class JsonHelper
{
    private static readonly JsonSerializerOptions RemoveCommentOptions = new()
    {
        AllowTrailingCommas = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
    };
    private static readonly JsonSerializerOptions IntendOptions = new()
    {
        WriteIndented = true,
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    };
    private static readonly JsonSerializerOptions NotIntendOptions = new()
    {
        WriteIndented = false,
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    };

    /// <summary>
    /// 格式化json
    /// </summary>
    /// <param name="srcJson">原json</param>
    /// <param name="isIntend">是否缩进</param>
    public static string Format(string srcJson, bool isIntend = true)
    {
        if (srcJson.IsNullOrWhiteSpace()) return srcJson;
        using var doc = JsonSerializer.Deserialize<JsonDocument>(srcJson, RemoveCommentOptions);
        var res = JsonSerializer.Serialize(doc, isIntend ? IntendOptions : NotIntendOptions);
        return res;
    }

    /// <summary>
    /// 判断json是否是数组(前面有注释或空白符也不影响判断)
    /// </summary>
    public static bool IsArray(string srcJson) => ParseJsonType(srcJson) == JsonValueKind.Array;
    /// <summary>
    /// 判断json是否是对象(前面有注释或空白符也不影响判断)
    /// </summary>
    public static bool IsObject(string srcJson) => ParseJsonType(srcJson) == JsonValueKind.Object;

    public static JsonValueKind ParseJsonType(string srcJson)
    {
        if (srcJson == null) return JsonValueKind.Undefined;

        var index = 0;
        var len = srcJson.Length;
        var singleComment = false;//当前在单行注释中
        var multiComment = false;//当前在多行注释中
        var preMultiEnd = false;//差一个 '/' 就结束多行注释
        var preComment = false;//差一个 '*' 或 '/' 就进入多行或单行注释
        while (index < len)
        {
            var c = srcJson[index];
            index++;
            #region 当前在单行注释中
            if (singleComment)
            {
                if (c == '\n')
                {
                    singleComment = false;
                }
                continue;
            }
            #endregion
            #region 当前在多行注释中
            if (multiComment)
            {
                if (preMultiEnd)
                {
                    //当前即将结束多行注释
                    if (c == '/')
                    {
                        //结束多行注释
                        multiComment = false;
                        continue;
                    }
                    else if (c == '*')
                    {
                        continue;
                    }
                    else
                    {
                        //状态失效
                        preMultiEnd = false;
                        continue;
                    }
                }
                else
                {
                    if (c == '*') preMultiEnd = true;
                    continue;
                }
            }
            #endregion

            #region 即将进入注释
            if (preComment)
            {
                if (c == '*')
                {
                    //进入多行
                    multiComment = true;
                    preComment = false;
                    continue;
                }
                else if (c == '/')
                {
                    //进入单行
                    singleComment = true;
                    preComment = false;
                    continue;
                }
                preComment = false;
                continue;
            }
            #endregion

            //啥模式也没有
            if (char.IsWhiteSpace(c)) continue;
            if (c == '/')
            {
                //预进入单行或多行注释模式
                preComment = true;
                continue;
            }
            if (c == '[') return JsonValueKind.Array;
            if (c == '{') return JsonValueKind.Object;
            if (c == '\"') return JsonValueKind.String;
            if (char.ToLower(c) == 't') return JsonValueKind.True;//true
            if (char.ToLower(c) == 'f') return JsonValueKind.False;//false
            if (char.ToLower(c) == 'n') return JsonValueKind.Null;//null
            if (c == '-' || (c >= 48 && c <= 57)) return JsonValueKind.Number;//-2,1.12
            return JsonValueKind.Undefined;
        }
        return JsonValueKind.Undefined;
    }

    /// <summary>
    /// 获取默认的 <c>JsonSerializerOptions</c>, 配置如下：
    /// <list type="bullet">
    /// <item>输出时 中文原样输出,而不是 \uxxxx; Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping</item>
    /// <item>输出时 包含字段; IncludeFields = true</item>
    /// <item>输出时 不包含只读字段; IgnoreReadOnlyFields = true</item>
    /// <item>输出时 忽略循环引用; ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles</item>
    /// <item>读取时 允许多余的逗号; AllowTrailingCommas = true</item>
    /// <item>读取时 跳过注释; ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip</item>
    /// <item>读取时 允许字符串转数字; NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString</item>
    /// <item>读取时 大小写不敏感; PropertyNameCaseInsensitive = true</item>
    /// </list>
    /// </summary>
    public static JsonSerializerOptions GetDefaultJsonSerializerOptions(
        string dateTimeFormatString = null,
        string dateTimeOffsetFormatString = null,
        string dateOnlyFormatString = null,
        string timeOnlyFormatString = null,
        bool enum2String = false
        )
    {
        return _getDefaultJsonSerializerOptions(dateTimeFormatString, dateTimeOffsetFormatString, dateOnlyFormatString, timeOnlyFormatString, enum2String);
    }

    internal static JsonSerializerOptions _getDefaultJsonSerializerOptions(
        string dateTimeFormatString = null,
        string dateTimeOffsetFormatString = null,
        string dateOnlyFormatString = null,
        string timeOnlyFormatString = null,
        bool enum2String = false,
        bool ignoreNull = false,
        bool lowerCamelCase = false,
        bool lowerCamelCaseDictionaryKey = false,
        bool isIntend = false,
        bool number2String = false,
        bool ignoreJsonPropertyName = false,
        bool readOnly = false
        )
    {
        if (readOnly)
        {
            var key = $"{dateTimeFormatString}:{dateTimeOffsetFormatString}:{dateOnlyFormatString}:{timeOnlyFormatString}:{enum2String}:{ignoreNull}:{lowerCamelCase}:{lowerCamelCaseDictionaryKey}:{isIntend}:{number2String}:{ignoreJsonPropertyName}";
            return _cache.GetOrAdd(key, act);
        }
        else
        {
            return act();
        }
        JsonSerializerOptions act()
        {
            var options = new JsonSerializerOptions();
            Configure(options,
                dateTimeFormatString: dateTimeFormatString,
                dateTimeOffsetFormatString: dateTimeOffsetFormatString,
                dateOnlyFormatString: dateOnlyFormatString,
                timeOnlyFormatString: timeOnlyFormatString,
                ignoreNull: ignoreNull,
                enum2String: enum2String,
                lowerCamelCase: lowerCamelCase,
                lowerCamelCaseDictionaryKey: lowerCamelCaseDictionaryKey,
                isIntend: isIntend,
                number2String: number2String,
                ignoreJsonPropertyName: ignoreJsonPropertyName);
            return options;
        }
    }

    private static readonly ConcurrentDictionary<string, JsonSerializerOptions> _cache = [];

    /// <summary>
    /// 对已有的 <c>JsonSerializerOptions</c> 进行配置,以增强兼容性,可用在 web开发中,如:
    /// <code>
    /// services.AddControllers().AddJsonOptions(options =>JsonHelper.Configure(options.JsonSerializerOptions, dateTimeFormatString: "yyyy-MM-dd", lowerCamelCase: true));
    /// </code>
    /// </summary>
    /// <param name="options">源对象</param>
    /// <param name="dateTimeFormatString">日期时间格式(DateTime)</param>
    /// <param name="dateTimeOffsetFormatString">日期时间格式(DateTimeOffset)</param>
    /// <param name="dateOnlyFormatString">日期时间格式(DateOnly)</param>
    /// <param name="timeOnlyFormatString">日期时间格式(TimeOnly)</param>
    /// <param name="enum2String">是否将枚举转换为字符串</param>
    /// <param name="ignoreNull">是否忽略null值的属性</param>
    /// <param name="lowerCamelCase">属性名称的首字母是否小写</param>
    /// <param name="lowerCamelCaseDictionaryKey">字典key首字母是否小写</param>
    /// <param name="isIntend">是否格式缩进</param>
    /// <param name="number2String">是否将数字转为字符串</param>
    /// <param name="ignoreJsonPropertyName">是否忽略注解: [JsonPropertyName("xxxx")]</param>
    public static void Configure(JsonSerializerOptions options,
        string dateTimeFormatString = null,
        string dateTimeOffsetFormatString = null,
        string dateOnlyFormatString = null,
        string timeOnlyFormatString = null,
        bool enum2String = false,
        bool ignoreNull = false,
        bool lowerCamelCase = false,
        bool lowerCamelCaseDictionaryKey = false,
        bool isIntend = false,
        bool number2String = false,
        bool ignoreJsonPropertyName = false)
    {
        //中文直接输出,而不是 \uxxxx 形式
        options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
        //允许输出Field
        options.IncludeFields = true;
        //忽略只读Field
        options.IgnoreReadOnlyFields = true;
        //忽略循环引用
        options.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        //允许存在多余的逗号
        options.AllowTrailingCommas = true;
        //跳过注释
        options.ReadCommentHandling = JsonCommentHandling.Skip;
        //允许将 "123" 转为数字 123
        //允许 {"Prop":"NaN"} 浮点型,但不允许 {\"Prop\":NaN},这点无法做到与Newtonsoft兼容
        options.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
        //允许忽略属性名大小写
        options.PropertyNameCaseInsensitive = true;

        //解决 "" => int? 报错问题
        options.Converters.Add(new NullAbleConverter());
        //解决 1 => bool 报错问题
        options.Converters.Add(new JsonConverterBool());
        //解决 "Open" => enum 报错问题
        options.Converters.Add(new JsonEnumConverter(enum2String));
        //解决反序列化问题: 123 => "123"
        options.Converters.Add(new JsonConverterString());

        //DateOnly & TimeOnly
        if (Environment.Version.Major == 6)
        {
            if (dateOnlyFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyFormatString));
            else options.Converters.Add(new JsonConverterDateOnly());
            if (timeOnlyFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterTimeOnly(timeOnlyFormatString));
            else options.Converters.Add(new JsonConverterTimeOnly());
        }
        else
        {
            if (dateOnlyFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyFormatString));
            if (dateOnlyFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyFormatString));
            if (timeOnlyFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterTimeOnly(timeOnlyFormatString));
        }
        //Datetime & DateTimeOffset
        if (dateTimeFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterDatetime(dateTimeFormatString));
        else options.Converters.Add(new JsonConverterDatetime());
        if (dateTimeOffsetFormatString.IsNotNullOrWhiteSpace()) options.Converters.Add(new JsonConverterDateTimeOffset(dateTimeOffsetFormatString));
        else options.Converters.Add(new JsonConverterDateTimeOffset());

        if (ignoreNull) options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        if (lowerCamelCase) options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        if (lowerCamelCaseDictionaryKey) options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
        if (isIntend) options.WriteIndented = true;
        if (number2String) options.NumberHandling |= JsonNumberHandling.WriteAsString;
        if (ignoreJsonPropertyName) options.TypeInfoResolver = new DefaultJsonTypeInfoResolver
        {
            Modifiers = {
                typeInfo =>
                {
                    for (int i = 0; i < typeInfo.Properties.Count; i++)
                    {
                        var property = typeInfo.Properties[i];
                        var prop = property.AttributeProvider as PropertyInfo;
                        if (prop != null) property.Name = lowerCamelCase?Char.ToLower(prop.Name[0]) + prop.Name[1..]:prop.Name;
                    }
                }
            }
        };
    }
}
